package jtg.generator;

import freemarker.template.Configuration;
import freemarker.template.Template;
import freemarker.template.TemplateExceptionHandler;
import freemarker.cache.ClassTemplateLoader;

import java.io.FileWriter;
import java.lang.reflect.Method;
import java.util.*;

public class JUnitTestGenerator {

    public static void generateJUnitTestClass(String classPath, String className, String methodName, List<Object[]> testCase) {
        try {
            // Load the class dynamically
            Class<?> targetClass = Class.forName(classPath + "." + className);

            // Find the method with the specified name
            Method targetMethod = null;
            for (Method method : targetClass.getDeclaredMethods()) {
                if (method.getName().equals(methodName)) {
                    targetMethod = method;
                    break;
                }
            }

            if (targetMethod == null) {
                System.out.println("Method not found: " + methodName);
                return;
            }

            List<String> methodSig = getMethodSignature(targetMethod);
            // Validate the test cases input type
            for (Object[] test : testCase){
                validateReturnTypeAndArgs(methodSig, test);
            }

            Configuration cfg = new Configuration(Configuration.VERSION_2_3_31);
            cfg.setTemplateLoader(new ClassTemplateLoader(JUnitTestGenerator.class, "/templates"));
            cfg.setDefaultEncoding("UTF-8");
            cfg.setTemplateExceptionHandler(TemplateExceptionHandler.RETHROW_HANDLER);

            // Provide data for the template
            Map<String, Object> data = new HashMap<>();
            data.put("classPath", classPath);
            data.put("className", className);
            data.put("methodName", methodName);
            data.put("methodSignature", methodSig);
            data.put("testCase", testCase);

            // Get the template
            Template template = cfg.getTemplate("junit_test_template.ftl");

            // Create output file
            String outputPath = "src/test/java/jUnitTest/" + className + "Test.java";
            FileWriter writer = new FileWriter(outputPath);

            // Process the template
            template.process(data, writer);

            System.out.println("JUnit test code generated successfully at: " + outputPath);

        } catch (ClassNotFoundException e) {
            System.out.println("Class not found: " + classPath + "." + className);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    private static List<String> getMethodSignature(Method method) {
        List<String> signature = new ArrayList<>();

        // Add return type
        signature.add(formatTypeName(method.getReturnType().getName()));

        // Add parameter types
        Class<?>[] parameterTypes = method.getParameterTypes();
        for (Class<?> paramType : parameterTypes) {
            signature.add(formatTypeName(paramType.getName()));
        }

        return signature;
    }

    private static String formatTypeName(String typeName) {
        // If typeName is a primitive type, convert it to its corresponding wrapper class
        switch (typeName) {
            case "short":
            case "java.lang.Short":
                return "Short";
            case "long":
            case "java.lang.Long":
                return "Long";
            case "double":
            case "java.lang.Double":
                return "Double";
            case "float":
            case "java.lang.Float":
                return "Float";
            case "byte":
            case "java.lang.Byte":
                return "Byte";
            case "char":
            case "java.lang.Char":
                return "Char";
            case "int":
            case "java.lang.Integer":
                return "Integer";
            case "boolean":
            case "java.lang.Boolean":
                return "Boolean";
            case "java.lang.String":
                return "String";
            default:
                return typeName;
        }
    }

    private static void validateReturnTypeAndArgs(List<String> methodSignature, Object[] testCase) {
        if (methodSignature.size() != testCase.length) {
            throw new IllegalArgumentException("Method signature and test case size mismatch at count");
        }
        for (int i = 0; i < methodSignature.size(); i++) {
            String expectedType = methodSignature.get(i);
            Object arg = testCase[i];

            if (!isMatchingType(expectedType, arg)) {
                throw new IllegalArgumentException("Type mismatch in method signature and test case at index " + i);
            }
        }
    }

    private static boolean isMatchingType(String expectedType, Object value) {
        if (value == null) return false;
        if (value.getClass().getName().substring(
                value.getClass().getName().lastIndexOf(".") + 1).equals(expectedType.substring
                (expectedType.lastIndexOf(".") + 1)
        ))
            return true;
        // Convert the expectedType to a Class object
        Class<?> expectedClass = getTypeClass(expectedType);

        // Check if the actual value is an instance of the expected type
        if (expectedClass.isPrimitive()) {
            return formatTypeName(expectedClass.getName()).equals(formatTypeName(value.getClass().getName()));
        } else {
            return expectedClass.isInstance(value);
        }

    }

    private static Class<?> getTypeClass(String typeName) {
        switch (typeName) {
            case "int":
            case "Integer":
                return Integer.TYPE;
            case "long":
            case "Long":
                return Long.TYPE;
            case "float":
            case "Float":
                return Float.TYPE;
            case "double":
            case "Double":
                return Double.TYPE;
            case "short":
            case "Short":
                return Short.TYPE;
            case "byte":
            case "Byte":
                return Byte.TYPE;
            case "char":
            case "Character":
                return Character.TYPE;
            case "boolean":
            case "Boolean":
                return Boolean.TYPE;
            case "String":
                return String.class;
            default:
                throw new IllegalArgumentException("Invalid type name: " + typeName);
        }
    }





    public static void main(String[] args) {
        // Example usage with test cases
        List<Object[]> testCases = new ArrayList<>();
        testCases.add(new Object[]{1, 2, 3});
        generateJUnitTestClass("jtg.optimizer", "ParameterType", "isNumber", testCases);

    }
}

